//this header file is used to contain all the 
//data structure we are gonna use during building
//up the 3d engine , finally the datastructures 
//in this file will be split into different classes
//and will be redigned to supply with a better 
//api , now it's the fastest to make the 1st draft
//version of the engine up and running , so we are 
//gonna do it.

#define PI ((float)3.1415926)

#define DEGREE_TO_RADIAN(degree) ((degree) * PI / 180.0)
#define RADIAN_TO_DEGREE(randian) ((radian) * 180.0 / PI)

typedef struct{
	union{
		float m[4];
		struct{
			float x , y , z , w;
		};
	};
}Point4D , * Point4DPtr , Vector4D , * Vector4DPtr;

typedef struct{
	Point4D verties[3];
}Poly4D , * Poly4DPtr;

typedef struct{
	Point4D p;
	Vector4D n;
}Plane3D , * Plane3DPtr;

typedef struct{
	union{
		float m[4][4];

		struct{
			float m00, m01, m02, m03;
			float m10, m11, m12, m13;
			float m20, m21, m22, m23;
			float m30, m31, m32, m33;
		};
	};

}Matrix4x4 , * Matrix4x4Ptr;

typedef struct{
	Point4D pos;

	Vector4D dir;
	Vector4D u , v , n;
	Vector4D target;

	float view_distance;

	float fov;
	
	float near_clip_z , far_clip_z;

	Plane3D right_clip_plane , left_clip_plane, 
		    top_clip_plane , bottom_clip_plane;
	float view_plane_width , view_plane_height;

	float view_screen_width , view_screen_height;

	float view_screen_center_x , view_screen_center_y;

	float aspect_ratio;

	Matrix4x4 transform_world_to_camera;
	Matrix4x4 transform_camera_to_perspective;
	Matrix4x4 transform_perspective_to_screen;
}Camera4D , * Camera4DPtr;

void InitVector4D(Vector4DPtr v , float x , float y , float z , float w){
	v->x = x;
	v->y = y;
	v->z = z;
	v->w = w;
}

void Vector4DCopy(Vector4DPtr source , Vector4DPtr target){
	target->x = source->x;
	target->y = source->y;
	target->z = source->z;
	target->w = source->w;
}

void MakeIdentityMatrix4D4x4(Matrix4x4Ptr matrix){
	matrix->m00 = 1; matrix->m01 = 0; matrix->m02 = 0; matrix->m03 = 0;
	matrix->m10 = 0; matrix->m11 = 1; matrix->m12 = 0; matrix->m13 = 0;
	matrix->m20 = 0; matrix->m21 = 0; matrix->m22 = 1; matrix->m23 = 0;
	matrix->m30 = 0; matrix->m31 = 0; matrix->m32 = 0; matrix->m33 = 1;
}

void Plane3DInit(Plane3DPtr plane , Point4DPtr p , Vector4DPtr n){
	Vector4DCopy(p , &plane->p);
	Vector4DCopy(n , &plane->n);
}

void InitCamera4D(Camera4DPtr cam , Point4DPtr pos , Vector4DPtr dir , Vector4DPtr target , float near_clip_z , float far_clip_z , float fov , float view_screen_width , float view_screen_height){
	Vector4DCopy(pos , &cam->pos);
	Vector4DCopy(dir , &cam->dir);
	
	cam->near_clip_z = near_clip_z;
	cam->far_clip_z = far_clip_z;
	
	cam->fov = fov;

	cam->view_screen_width = view_screen_width;
	cam->view_screen_height = view_screen_height;

	cam->view_screen_center_x = (cam->view_screen_width - 1) / 2;
	cam->view_screen_center_y = (cam->view_screen_height - 1) / 2;

	MakeIdentityMatrix4D4x4(&cam->transform_world_to_camera);
	MakeIdentityMatrix4D4x4(&cam->transform_camera_to_perspective);
	MakeIdentityMatrix4D4x4(&cam->transform_perspective_to_screen);

	cam->fov = fov;

	cam->aspect_ratio = cam->view_screen_width / cam->view_screen_height;

	//TODO
	cam->view_plane_width = 2;
	cam->view_plane_height = 2 / cam->aspect_ratio;

	float tan_fov_div2 = (float)(DEGREE_TO_RADIAN(fov / 2));

	//TODO i think the book is wrong here
	cam->view_distance = (float)(0.5 * cam->view_plane_width / tan_fov_div2);

	Point4D origin = {0 , 0 , 0 , 1};

	Vector4D normal;

	InitVector4D(&normal , cam->view_distance , 0 , - cam->view_plane_width / 2 , 1);
	Plane3DInit(&cam->right_clip_plane , &origin , &normal);

	InitVector4D(&normal , - cam->view_distance , 0 , - cam->view_plane_width / 2 , 1);
	Plane3DInit(&cam->left_clip_plane , &origin , &normal);

	InitVector4D(&normal , 0 , cam->view_distance , - cam->view_plane_height / 2 , 1);
	Plane3DInit(&cam->top_clip_plane , &origin , &normal);

	InitVector4D(&normal , 0 , - cam->view_distance , - cam->view_plane_height / 2 , 1);
	Plane3DInit(&cam->bottom_clip_plane , &origin , &normal);
}